package cn.melonlib.cores.jpa.comp.dao;

import cn.melonlib.cores.commons.comp.utils.HibernateQlParamsTool;
import cn.melonlib.cores.commons.model.entity.IEntity;
import cn.melonlib.cores.jpa.comp.Dao;
import cn.melonlib.cores.jpa.model.bean.PersistenceResult;
import cn.melonlib.cores.jpa.model.bean.Searcher;
import cn.melonlib.cores.jpa.model.bean.adapter.*;
import cn.melonlib.cores.jpa.model.bean.collections.DataMap;
import cn.melonlib.cores.jpa.model.bean.impl.*;
import lombok.Getter;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.query.internal.NativeQueryImpl;
import org.hibernate.transform.Transformers;
import org.intellij.lang.annotations.Language;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;

import javax.annotation.PostConstruct;
import javax.persistence.*;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaDelete;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.EntityType;
import java.io.Serializable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

@Repository("dao")
@Lazy(false)
@Getter
@ConditionalOnProperty(value = "jpa.dao.type",havingValue = "hibernate",matchIfMissing = true)
public class HibernateDao extends ApplicationObjectSupport implements Dao, ApplicationRunner {

    @Value("${jdbc.url}")
    private String dbUrl;

    @Value("${jdbc.username}")
    private String dbUser;

    private DataMap<Class<?>, Searcher> searchers=new DataMap<>();

    @PersistenceContext
    private EntityManager entityManager;

    private Map<String, EntityType> types=new HashMap<>();

    private boolean init;

    public HibernateDao() {
        this.initSearcher();
    }

    @PostConstruct
    public synchronized void init(){
        logger.info("EntityManager:"+Thread.currentThread().getId());
        this.printDbInfo();
        this.initTypes();
        init=true;
        System.out.println(this.entityManager);
        this.notifyAll();
    }

    public synchronized void lock() throws Exception{
        if(!init) {
            this.wait();
        }
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
//        logger.info("EntityManager:"+Thread.currentThread().getId());

    }

    @Override
    public boolean isInit() {
        return init;
    }

    @Override
    public <DATA, QUERY extends CharSequence> PersistenceResult<DATA> search(QUERY query) {
        if(query==null){
            return new DefaultQueryResult(new DefaultResultMessage("没有查询语句！"));
        }
        Searcher searcher= ObjectUtils.defaultIfNull(searchers.get(query.getClass()),searchers.get(String.class));
        if(searcher instanceof SimpleSearcher){
            return new DefaultQueryResult(searcher.search(query.toString(),this.entityManager));
        }
        return new DefaultQueryResult(searcher.search(query,this.entityManager));
    }

    @Override
    public <DATA> PersistenceResult<DATA> query(Function<EntityManager, DATA> worker) {
        return new DefaultUpdateResult<>(worker.apply(this.entityManager));
    }

    @Override
    public <DATA, PK extends Serializable> DATA getEntity(Class<DATA> template, PK id) {
        return this.entityManager.find(template,id);
    }

    @Override
    public <ENTITY extends IEntity> PersistenceResult<ENTITY> save(ENTITY entity) {
        return new DefaultUpdateResult(this.entityManager.merge(entity));
    }

    @Override
    public <ENTITY extends IEntity> PersistenceResult<Integer> remove(ENTITY entity) {
        Assert.notNull(entity,"不能删除空数据！");
        this.entityManager.remove(entity);
        return new DefaultUpdateResult(1);
    }


    public PersistenceResult<Integer> removeById(Class<? extends IEntity> entityClass, Serializable id) {
        Assert.notNull(entityClass,"数据模板不能为空！");
        Assert.notNull(id,"数据主键不能为空！");
        IEntity entity=this.entityManager.find(entityClass,id);
        Assert.notNull(entity,"没有找到数据！");
        this.entityManager.remove(entity);
        return new DefaultUpdateResult(1);
    }

    @Override
    public PersistenceResult<Integer> removeMulti(List<? extends  IEntity> entities) {
        Assert.notEmpty(entities,"没有可以删除的对象");
        CriteriaBuilder criteriaBuilder=this.entityManager.getCriteriaBuilder();
        CriteriaDelete<? extends IEntity> criteriaDelete=criteriaBuilder.createCriteriaDelete(entities.get(0).getClass());
        Class cls=entities.get(0).getClass();
        Root root = criteriaDelete.from(cls);
        CriteriaBuilder.In in=criteriaBuilder.in(root.get("id"));
        entities.stream().map(IEntity::getId).forEach(in::value);
        criteriaDelete.where(in);
        return new DefaultUpdateResult(entityManager.createQuery(criteriaDelete).executeUpdate());
    }

    @Override
    public PersistenceResult<Integer> removeMultiByKeys(String target, String ids) {
        Assert.isTrue(StringUtils.isNotBlank(target),"没有指定删除的对象");
        Assert.isTrue(StringUtils.isNotBlank(ids),"没有删除的主键数据");
        List<? extends Serializable> idList= Arrays.stream(ids.split(",")).collect(Collectors.toList());
        return this.removeMultiByKeys(target,idList);
    }

    @Override
    public PersistenceResult<Integer> removeMultiByKeys(String target, List<? extends Serializable> ids) {
        Assert.notEmpty(ids,"没有删除的主键数据");
        return this.removeMultiByKeys(this.getTypeClass(target),ids);
    }
    @Override
    public PersistenceResult<Integer> removeMultiByKeys(Class target, List<? extends Serializable> ids) {
        Assert.notEmpty(ids,"没有删除的主键数据");
        CriteriaBuilder criteriaBuilder=this.entityManager.getCriteriaBuilder();
        CriteriaDelete<? extends IEntity> criteriaDelete=criteriaBuilder.createCriteriaDelete(target);
        Root root = criteriaDelete.from(target);
        CriteriaBuilder.In in=criteriaBuilder.in(root.get("id"));
        ids.stream().forEach(in::value);
        criteriaDelete.where(in);
        return new DefaultUpdateResult(entityManager.createQuery(criteriaDelete).executeUpdate());
    }

    @Override
    public PersistenceResult<Integer> update(Function<EntityManager, Integer> worker) {
        return new DefaultUpdateResult(worker.apply(entityManager));
    }

    @Override
    public Dao flush() {
        this.entityManager.flush();
        return this;
    }

    @Override
    public Dao open() {
        return this;
    }

    @Override
    public Dao close() {
        this.entityManager.close();
        return this;
    }

    @Override
    public Dao commit() {
        return this;
    }

    @Override
    public Dao rollback() {
        return this;
    }


    @Override
    public EntityType getType(String name) {
        return this.types.get(name);
    }

    public Class getTypeClass(String name){
        return this.types.get(name).getBindableJavaType();
    }

    public Map<String,EntityType> getAllTypes(){
        return this.types;
    }

    @Override
    public int updateQl(@Language("HQL") String ql, List<? extends Serializable> params) {
        Query query=this.entityManager.createQuery(ql);
        HibernateQlParamsTool.setQueryParam(query,params);
        return query.executeUpdate();
    }

    @Override
    public <VO> List<VO> queryToVo(Query query, Class<VO> voClass) {
        query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.aliasToBean(voClass));
        return query.getResultList();
    }

    private void initSearcher(){
        searchers.append(String.class,new SimpleSearcher())
            .append(DefaultQuery.class,new DefaultSearcher())
            .append(SingleQuery.class, new SingleSearcher())
            .append(PaginationQueryImpl.class,new PaginationSearcher())
            .append(OffsetQueryImpl.class,new OffsetSearcher());
    }

    private void initTypes(){
        this.entityManager.getMetamodel().getEntities().stream().forEach(type->types.put(type.getName(),type));
    }

    private void printDbInfo(){
        StringBuilder dbInfo=new StringBuilder("\r\n");
        dbInfo.append("*************************** 数据库连接信息 ***************************\r\n");
        dbInfo.append("数据库连接：").append(dbUrl);
        dbInfo.append("\r\n数据库用户：").append(dbUser);
        logger.info(dbInfo);
    }

    public <ENTITY extends IEntity<? extends Serializable>> ENTITY lock(ENTITY entity){
        entityManager.lock(entity, LockModeType.PESSIMISTIC_WRITE);
        return entity;
    }
    
}
